package com.smsc.headend.connector.ping;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.connector.ping.dto.SendPto;
import com.smsc.headend.module.ping.PingParams;
import com.smsc.headend.module.ping.PingResponseDto;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.StringJoiner;

/**
 * @author qinbin
 * @date 2022/7/18 16:47
 */
@Slf4j
public class PingUtil {
    private static final String IPV6_HEADER_VERSION = "60 00 00 00";
    private static final String IPV6_HEADER_NEXT_HEADER_TYPE = "3A";
    private static final String IPV6_HEADER_HOP_LIMIT = "40";
    private static final String DEST_HEAD = "20 01 0D B8 00 00 00 00";

    /**
     * IPv6地址的格式
     */
    public static final String IPV6_REG = "([\\da-fA-F]{1,4}:){7}[\\da-fA-F]{1,4}$"
            + "|^:((:[\\da-fA-F]{1,4}){1,6}|:)$"
            + "|^[\\da-fA-F]{1,4}:((:[\\da-fA-F]{1,4}){1,5}|:)$"
            + "|^([\\da-fA-F]{1,4}:){2}((:[\\da-fA-F]{1,4}){1,4}|:)$"
            + "|^([\\da-fA-F]{1,4}:){3}((:[\\da-fA-F]{1,4}){1,3}|:)$"
            + "|^([\\da-fA-F]{1,4}:){4}((:[\\da-fA-F]{1,4}){1,2}|:)$"
            + "|^([\\da-fA-F]{1,4}:){5}:([\\da-fA-F]{1,4})?$"
            + "|^([\\da-fA-F]{1,4}:){6}:";

    public static PingResponseDto pingMeter(PingParams pingParams) {
        log.info("Ping Params{}", JSONUtil.toJsonStr(pingParams));
        DatagramSocket datagramSocket = null;
        PingResponseDto pingResponseDto = new PingResponseDto();
        pingResponseDto.setDeviceId(pingParams.getDeviceId());
        pingResponseDto.setMsgSn(pingParams.getMsgSn());
        pingResponseDto.setSeq(pingParams.getSeq());
        Long startTime = System.currentTimeMillis();
        try {
            datagramSocket = new DatagramSocket(pingParams.getLocalPort());
            datagramSocket.setSoTimeout(pingParams.getTimeOut());
            byte[] ipv6Bytes = ByteUtils.hexStringToByte(parseFullIpv6(pingParams.getHesIpv6()));
            String sendPacketStr = generatorPacket(pingParams.getMsgSn(), ByteUtils.byteToHexString(ipv6Bytes), pingParams.getPacketLen(), pingParams.getSeq());
            log.info("Ping Send Packet:{}", sendPacketStr);
            byte[] bs = ByteUtils.hexStringToByte(sendPacketStr);
            DatagramPacket sendPacket = new DatagramPacket(bs, bs.length, new InetSocketAddress(pingParams.getHost(), pingParams.getPort()));
            datagramSocket.send(sendPacket);

            //封装成包 DatagramPacket(byte[] buf,int lenth)  相当于构建接受池
            byte[] bytes = new byte[2048];
            DatagramPacket receivePacket = new DatagramPacket(bytes, bytes.length);
            //接收客户端发来的数据
            datagramSocket.receive(receivePacket);
            Long endTime = System.currentTimeMillis();
            //获取接收到的数据
            byte[] data = ArrayUtil.sub(receivePacket.getData(), 0, receivePacket.getLength());
            log.info("Ping Rece Packet:{}", ByteUtils.byteToHexString(data));
            Long packetLatency = getPacketLatency(data);
            String meterAddr = getPacketSourceAddr(data);
            //获取接收到的数据的长度
            int len = receivePacket.getLength();
            //输出相关的信息
            log.info("system latency:{},packet latency:{},len:{},meter addr;{}", endTime - startTime, packetLatency, len, meterAddr);
            //时延
            pingResponseDto.setPacketLatency(packetLatency);
            //耗时
            pingResponseDto.setUseTime(endTime - startTime);
            pingResponseDto.setStatus(1);
            pingResponseDto.setMeterAddr(meterAddr);
        } catch (Exception e) {
            log.error("pingMeter error:{}", e);
            Long endTime = System.currentTimeMillis();
            pingResponseDto.setUseTime(endTime - startTime);
            pingResponseDto.setStatus(0);
        } finally {
            if (null != datagramSocket) {
                datagramSocket.close();

            }
        }
        return pingResponseDto;
    }

    /**
     * 生成IPv6数据包
     *
     * @param msgSn
     * @param hesIpv6
     * @param dataLen
     * @param seq
     * @return
     */
    public static String generatorPacket(String msgSn, String hesIpv6, Integer dataLen, Integer seq) {
        SendPto sendPto = new SendPto();
        String payloadLength = ByteUtils.byteToHexString(ByteUtils.intToByte(dataLen + 8, 2));
        String ipv6Header = IPV6_HEADER_VERSION + " " + payloadLength + " " + IPV6_HEADER_NEXT_HEADER_TYPE + " " + IPV6_HEADER_HOP_LIMIT;
        sendPto.setVersion(ipv6Header);
        sendPto.setSourceAddr(hesIpv6);
        byte[] msgSnByte = ByteUtils.longToByte(Long.parseLong(msgSn), 8);
        String destAddr = DEST_HEAD + " " + ByteUtils.byteToHexString(msgSnByte);
        sendPto.setDestAddr(destAddr);
        byte[] data = new byte[dataLen];
        sendPto.setData(ByteUtils.byteToHexString(data));
        byte[] seqByte = ByteUtils.intToByte(seq, 2);
        String checkSumOriginal = "80 00 00 00 00 01 " + ByteUtils.byteToHexString(seqByte);
        String checkSumStr = sendPto.getSourceAddr() + sendPto.getDestAddr() + "00 00 " + payloadLength + "00 00 00" + IPV6_HEADER_NEXT_HEADER_TYPE + checkSumOriginal + sendPto.getData();
        byte[] bytes = ByteUtils.hexStringToByte(checkSumStr);
        long checkSumNumber = PingUtil.checksum(bytes, bytes.length);
        String checkSumMsgStr = "80 00 " + ByteUtils.byteToHexString(ByteUtils.longToByte(checkSumNumber, 2)) + " 00 01 " + ByteUtils.byteToHexString(seqByte);
        sendPto.setCheckMsg(checkSumMsgStr);
        return sendPto.toString();
    }

    /**
     * 计算包延迟
     *
     * @param buffer
     * @return
     */
    public static Long getPacketLatency(byte[] buffer) {
        if (buffer.length > 56) {
            byte[] latency = ArrayUtil.sub(buffer, 48, 55);
            latency = ByteUtils.reverseByteArr(latency);
            String hexString = ByteUtils.byteToHexString(latency).replaceAll(" ", "");
            return Long.parseLong(hexString, 16);
        }
        return 0L;
    }

    /**
     * 获取报文源Ipv6地址
     *
     * @param buffer
     * @return
     */
    public static String getPacketSourceAddr(byte[] buffer) {
        String sourceBuffer = "";
        if (buffer.length > 8) {
            byte[] sourceAddr = ArrayUtil.sub(buffer, 24, 40);
            for (int i = 1; i <= sourceAddr.length; i++) {
                if (i % 2 == 0) {
                    sourceBuffer += ByteUtils.byteToHexString(ArrayUtil.sub(sourceAddr, i - 2, i)) + ":";
                }
            }
            sourceBuffer = sourceBuffer.substring(0, sourceBuffer.length() - 1).replaceAll(" ", "");
            sourceBuffer = compressIpv6(sourceBuffer);
        }
        return sourceBuffer;
    }

    /**
     * 将一个IPv6地址转为全写格式
     * 例：将1ade:03da:0::转为1ade:3da:0000:0000:0000:0000:0000:0000
     *
     * @param ipv6Str
     * @return fullIPv6
     */
    public static String parseFullIpv6(String ipv6Str) {
        // 判断IPv6地址的格式是否正确
        if (!ipv6Str.matches(IPV6_REG)) {
            return "";
        }
        String[] arr = new String[]{"0000", "0000", "0000", "0000", "0000", "0000", "0000", "0000"};
        // 将IPv6地址用::分开
        String[] tempArr = ipv6Str.split("::");
        // tempArr[0]用:分开，填充到arr前半部分
        if (tempArr.length > 0) {
            // new String[0]为空数组，因为"".split(":")为{""}，如果tempArr[0]==""，此时数组包含一个元素
            String[] tempArr0 = tempArr[0].isEmpty() ? new String[0] : tempArr[0].split(":");
            for (int i = 0; i < tempArr0.length; i++) {
                // 如果是纯数字，用parseInt去除前导0，如果包含字母，用正则去除前导0
                arr[i] = tempArr0[i].matches("\\d+")
                        ? (Integer.parseInt(tempArr0[i]) + "")
                        : tempArr0[i].replaceAll("^(0+)", "");
            }
        }
        // tempArr[1]用:分开，填充到arr后半部分
        if (tempArr.length > 1) {
            String[] tempArr1 = tempArr[1].isEmpty() ? new String[0] : tempArr[1].split(":");
            for (int i = 0; i < tempArr1.length; i++) {
                arr[i + arr.length - tempArr1.length] = tempArr1[i].matches("\\d+")
                        ? (Integer.parseInt(tempArr1[i]) + "")
                        : tempArr1[i].replaceAll("^(0+)", "");
            }
        }
        return StringUtils.join(arr, "");
    }

    private static String compressIpv6(String rawIpv6) {
        if (rawIpv6.length() != 39) {
            return rawIpv6;
        }
        int[] segments = Arrays.stream(rawIpv6.split(":")).mapToInt(s -> Integer.valueOf(s, 16)).toArray();

        // 寻找连续零区间的下标
        int[] index = startIndexAndLengthOfContinuousZero(segments);
        int startIndex = index[0];
        int endIndex = index[0] + index[1] - 1;

        StringJoiner joiner = new StringJoiner(":");
        for (int i = 0; i < segments.length; i++) {

            if (segments[i] == 0 && i >= startIndex && i <= endIndex) {
                // 连续零的一部分
                if (i == 0 || i == segments.length - 1) {
                    // 如果是连续零下标是首个或者最后一个时，补空字符串
                    joiner.add("");
                }
                if (i == endIndex) {
                    // 表示连续零的压缩
                    joiner.add("");
                }
                continue;
            }
            joiner.add(Integer.toHexString(segments[i]) + "");
        }
        return joiner.toString().toUpperCase();
    }

    private static int[] startIndexAndLengthOfContinuousZero(int[] segments) {
        int maxZeroLen = 0;
        int zeroStart = -1;
        int subLen = 0;
        int subStart = -1;
        for (int i = 0; i < segments.length; i++) {
            if (segments[i] == 0) {
                if (subLen == 0) {
                    // 连续零的第一个零出现
                    subStart = i;
                }
                subLen++;
                if (subLen > maxZeroLen) {
                    // 连续串长度超过了原来的最大串, 覆写结果
                    maxZeroLen = subLen;
                    zeroStart = subStart;
                }
            } else {
                subLen = 0;
                subStart = -1;
            }
        }
        if (maxZeroLen > 1) {
            return new int[]{zeroStart, maxZeroLen};
        } else {
            // 仅有一个0不算连续零
            return new int[]{-1, 0};
        }
    }

    public static long checksum(byte[] buf, int length) {
        int i = 0;
        long sum = 0;
        while (length > 0) {
            sum += (buf[i++] & 0xff) << 8;
            if ((--length) == 0) {
                break;
            }
            sum += (buf[i++] & 0xff);
            --length;
        }
        return (~((sum & 0xFFFF) + (sum >> 16))) & 0xFFFF;
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 1; i++) {
            PingParams pingParams = new PingParams();
            String msgSn = "5192001";
            String hesIpv6 = "fe80::f200:9ce0:2ace:268d";
            System.out.println(compressIpv6(hesIpv6));
            String host = "192.168.252.100";
            Integer localPort = 9085;
            Integer port = 3544;
            Integer dataLen = 1024;
            Integer timeOut = 5000;
            pingParams.setMsgSn(msgSn);
            pingParams.setHesIpv6(hesIpv6);
            pingParams.setHost(host);
            pingParams.setLocalPort(localPort);
            pingParams.setPort(port);
            pingParams.setTimeOut(timeOut);
            pingParams.setPacketLen(dataLen);
            pingParams.setSeq(i);
            PingUtil.pingMeter(pingParams);
        }
    }
}
